package openglsuperbible.glutils;

import java.io.*;
import java.nio.FloatBuffer;
import java.util.HashMap;
import java.util.Map;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.*;
import static org.lwjgl.opengl.GL20.*; 



public class Shader {
    
    private Map<String, Integer> attributeLocations = new HashMap<String, Integer>();
    private Map<String, Integer> uniformLocations = new HashMap<String, Integer>();
    
    private int program = 0;
    private int vertShader = 0;
    private int fragShader = 0;
    private boolean useShader = true;

    public Shader(String vert,String frag) {
        program = glCreateProgram();
        if(program!=0){
            vertShader= createVertShader(vert);
            fragShader= createFragShader(frag);
        }else useShader = false;
                
        if(vertShader !=0 && fragShader !=0){
            glAttachShader(program, vertShader);
            glAttachShader(program, fragShader);
            
            glLinkProgram(program);
                // These are no longer needed
            glDeleteShader(vertShader);
            glDeleteShader(fragShader);  
            String log = glGetProgramInfoLog(program, 65536);
            if (log.length() != 0) {
             System.err.println("Program link log:\n" + log);
            }  
            
        }else useShader = false;
        
        
        // set attributes
        int numAttributes = glGetProgram(program, GL_ACTIVE_ATTRIBUTES);
        int maxAttributeLength = glGetProgram(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH);
        for (int i = 0; i < numAttributes; i++) 
        {
            String name = glGetActiveAttrib(program, i, maxAttributeLength);
            int location = glGetAttribLocation(program, name);
            System.out.println(name + ":" + location);
            attributeLocations.put(name, location);          
        }
        
        // set uniforms
        int numUniforms = glGetProgram(program, GL_ACTIVE_UNIFORMS);
        int maxUniformLength = glGetProgram(program, GL_ACTIVE_UNIFORM_MAX_LENGTH);
        for (int i = 0; i < numUniforms; i++) 
        {
            String name = glGetActiveUniform(program, i, maxUniformLength);
            int location = glGetUniformLocation(program, name);
            uniformLocations.put(name, location);
            System.out.println(name + ":" + location);
        }  
        
    }
    
    
    private int createVertShader(String filename){

        //vertShader will be non zero if succefully created
        vertShader = glCreateShader(GL_VERTEX_SHADER);
         //if created, convert the vertex shader code to a String
        if(vertShader==0){return 0;}    
            String vertexCode="";
            String line;
        try{
            BufferedReader reader=new BufferedReader(new FileReader("shaders/" + filename + ".vert"));
            while((line=reader.readLine())!=null){
                vertexCode+=line + "\n";
            }
        }catch(Exception e){
            System.out.println("Fail reading vertex shading code");
            return 0;
        }
    
        /*
        * associate the vertex code String with the created vertex shader
        * and compile
        */    
        glShaderSource(vertShader, vertexCode);
        glCompileShader(vertShader);
        
        String vertexShaderErrorLog = GL20.glGetShaderInfoLog(vertShader, 65536);
        if (vertexShaderErrorLog.length() != 0) 
        {              
            System.err.println("Vertex shader compile log: \n" + vertexShaderErrorLog);
            vertShader=0; 
        }      
        //if zero we won't be using the shader
        return vertShader;
    }
    
    
    private int createFragShader(String filename){

        fragShader= glCreateShader(GL_FRAGMENT_SHADER);
        if(fragShader==0){return 0;}    
            String fragCode="";
            String line;

        try{
            BufferedReader reader=new BufferedReader(new FileReader("shaders/" + filename + ".frag"));
            while((line=reader.readLine())!=null){
                fragCode+=line + "\n";
            }
        }catch(Exception e){
            System.out.println("Fail reading fragment shading code");
            return 0;
        }   
   
        glShaderSource(fragShader, fragCode);
        glCompileShader(fragShader);
        
         String fragmentShaderErrorLog = GL20.glGetShaderInfoLog(fragShader, 65536); 
        if (fragmentShaderErrorLog.length() != 0) {  
            System.err.println("Fragment shader compile log: \n" + fragmentShaderErrorLog);  
            fragShader=0;
        }  
    
        //if zero we won't be using the shader
        return fragShader;
    }
    
   
    public void use()
    {   if(useShader) {
           glUseProgram(program);
        }
    }
    

    public void endUse(){
        glUseProgram(0);
    }
    
    public void free()
    {
        glDeleteShader(program);
    }
    
     public Map<String, Integer> getAttributeLocations() 
    {
        return attributeLocations;      
    } 

    public Map<String, Integer> getUniformLocations() 
    {
        return uniformLocations;
    } 

    public void setUniformMatrix4(String uniformName, FloatBuffer matrixdata) 
    {          
        int location = uniformLocations.get(uniformName);
        glUniformMatrix4(location, false, matrixdata);
    } 
    
    public void setUniformMatrix4(String uniformName, boolean transpose, FloatBuffer matrixdata) 
    {          
        int location = uniformLocations.get(uniformName);
        glUniformMatrix4(location, transpose, matrixdata);
    } 

    public void setUniformMatrix4(String uniformName, float[] matrixdata) 
    {          
        FloatBuffer buff = BufferUtils.createFloatBuffer(16);
        buff.put(matrixdata);
        buff.flip();
        int location = uniformLocations.get(uniformName);
        glUniformMatrix4(location, false, buff);
    }
    
    public void setUniformMatrix4(String uniformName, boolean transpose, float[] matrixdata) 
    {          
        FloatBuffer buff = BufferUtils.createFloatBuffer(16);
        buff.put(matrixdata);
        buff.flip();
        int location = uniformLocations.get(uniformName);
        glUniformMatrix4(location, transpose, buff);
    }
    
    
     public void setUniformMatrix3(String uniformName, FloatBuffer matrixdata) 
    {          
        int location = uniformLocations.get(uniformName);
        glUniformMatrix3(location, false, matrixdata);
    }
    
    public void setUniformMatrix3(String uniformName, boolean transpose, FloatBuffer matrixdata) 
    {          
        int location = uniformLocations.get(uniformName);
        glUniformMatrix3(location, transpose, matrixdata);
    }

    public void setUniformMatrix3(String uniformName, float[] matrixdata) 
    {   
        FloatBuffer buff = BufferUtils.createFloatBuffer(9);
        buff.put(matrixdata);
        buff.flip();
        int location = uniformLocations.get(uniformName);
        glUniformMatrix3(location, false, buff);
    }
    
    public void setUniformMatrix3(String uniformName, boolean transpose, float[] matrixdata) 
    {   
        FloatBuffer buff = BufferUtils.createFloatBuffer(9);
        buff.put(matrixdata);
        buff.flip();
        int location = uniformLocations.get(uniformName);
        glUniformMatrix3(location, transpose, buff);
    }
    
    public void setUniform1i(String uniformName, int i) 
    {          
        int location = uniformLocations.get(uniformName); 
        glUniform1i(location, i);
    }    
    
    public void setUniform1f(String uniformName, float f) 
    {          
        int location = uniformLocations.get(uniformName); 
        glUniform1f(location, f);
    } 

    public void setUniform3(String uniformName, float v1, float v2, float v3) 
    {          
        int locaiton = uniformLocations.get(uniformName); 
        glUniform3f(locaiton, v1, v2, v3);
    }

    public void setUniform3(String uniformName,float[] array) 
    {          
        int locaiton = uniformLocations.get(uniformName); 
        glUniform3f(locaiton, array[0],  array[1],  array[2]);
    }
    public void setUniform4(String uniformName, float v1, float v2, float v3, float v4) 
    {          
        int location = uniformLocations.get(uniformName);          
        glUniform4f(location, v1, v2, v3, v4);      
    }
    
    public void setUniform4(String uniformName, float[] array) 
    {          
        int location = uniformLocations.get(uniformName);          
        glUniform4f(location, array[0],  array[1],  array[2],  array[3]);      
    }
    
}
